using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MonsterTruckController : MonoBehaviour
{
    [Header("Vehicle Stats")]
    public float torque;
    public float angularSpeed = 100;
    public float MaxTurnAngle;
    public float downForce;
    public bool allWheelDrive = true;
    public bool allWheelTurn = false;
    public bool allWheelBrake = true;
    public float Raydistance;
    public float skidWidth = 0.5f;
    public LayerMask drivable;
    public Transform centerOfMass;

    public HingeJoint[] steeringJoint = new HingeJoint[4];
    public HingeJoint[] wheelJoints = new HingeJoint[4];

    [Header("Experimental")]
    public bool usingSoftWheelCollider = false;

    [Header("Audio settings")]
    public AudioSource engineSound;
    [Range(0, 1)]
    public float minPitch;
    [Range(1, 3)]
    public float maxPitch;

    private Rigidbody rb;
    private float steeringInput, accelerationInput, handBrakeInput;
    private bool isBraking = false;
    private bool AllWheelBraking = false;
    bool isGrounded = false;

    public Vector3 LocalVelocity
    {
        get { return transform.InverseTransformDirection(rb.linearVelocity); }
    }

    #region Editor/Context Menu Functions

    [ContextMenu("Add Soft Wheel Colliders")]
    public void AddSoftWheelColliders()
    {
        foreach (HingeJoint hj in wheelJoints)
        {
            if (hj.GetComponent<SoftWheelCollider>()) return;

            WheelSkid ws = hj.GetComponent<WheelSkid>();
            var newWS = hj.gameObject.AddComponent<WheelSkidSoft>();
            newWS.rb = ws.rb;
            newWS.smoke = ws.smoke;
            newWS.skidSound = ws.skidSound;
            newWS.tireImpact = ws.tireImpact;
            ws.enabled = false;

            var softWC = hj.gameObject.AddComponent<SoftWheelCollider>();
            softWC.springStrength = 5000f;
            softWC.springDamper = 50f;
            softWC.radius = hj.GetComponent<SphereCollider>().radius;
            softWC.wheelWidth = hj?.gameObject.GetComponentInChildren<MeshRenderer>()?.bounds.size.x ?? 1.25f;
            softWC.coefficientOfFriction = 1f;

            var removeLayer1 = transform.Find("Body Collider").gameObject.layer;
            var removeLayer2 = hj.gameObject.layer;
            softWC.drivableSurface = ~(1 << removeLayer1 | 1 << removeLayer2);
        }
        usingSoftWheelCollider = true;
    }

    [ContextMenu("Remove Soft Wheel Colliders")]
    public void RemoveSoftWheelColliders()
    {
        foreach (HingeJoint hj in wheelJoints)
        {
            if (!hj.GetComponent<SoftWheelCollider>()) return;

            hj.GetComponent<WheelSkid>().enabled = true;
            DestroyImmediate(hj.gameObject.GetComponent<WheelSkidSoft>());
            DestroyImmediate(hj.gameObject.GetComponent<SoftWheelCollider>());
        }
        usingSoftWheelCollider = false;
    }

    #endregion

    #region Unity Functions

    void Start()
    {
        rb = GetComponent<Rigidbody>();
        rb.centerOfMass = Vector3.zero;
        GameObject.FindFirstObjectByType<Skidmarks>().GetComponent<Skidmarks>().SkidmarkWidth = skidWidth;
    }

    private void Update()
    {
        // More concise check for isBraking
        isBraking = handBrakeInput > 0.1f;

        if (isGrounded && Mathf.Abs(steeringInput) < 0.1f && !usingSoftWheelCollider)
        {
            // Combine logic to determine AllWheelBraking
            if ((accelerationInput < -0.1f && LocalVelocity.z > 1) ||
                (accelerationInput > 0.1f && LocalVelocity.z < -1))
            {
                AllWheelBraking = true;
                isBraking = true;
            }
            else
            {
                AllWheelBraking = false;
            }
        }
        else
        {
            AllWheelBraking = false;
        }

        soundManager();
    }

    public void ProvideInputs(float _steeringInput, float _accelerationInput, float _handbrakeInput)
    {
        steeringInput = _steeringInput;
        accelerationInput = _accelerationInput;
        handBrakeInput = _handbrakeInput;
    }

    void FixedUpdate()
    {
        turnLogic();
        accelarationLogic();
        brakeLogic();

        if (grounded())
        {
            AddDownForce();
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        rb.centerOfMass = centerOfMass.localPosition;
    }

    private void OnCollisionExit(Collision collision)
    {
        rb.centerOfMass = Vector3.zero;
    }

    #endregion

    public bool grounded()
    {
        // Cache raycast result in a local variable to avoid repeated calls
        bool hitGround = Physics.Raycast(transform.position, -transform.up, out RaycastHit hit, Raydistance, drivable);
        isGrounded = hitGround;
        return hitGround;
    }

    void accelarationLogic()
    {
        if (AllWheelBraking) return;

        if (allWheelDrive)
        {
            foreach (HingeJoint wheel_jt in wheelJoints)
            {
                JointMotor motar = wheel_jt.motor;
                motar.targetVelocity = angularSpeed * accelerationInput;
                motar.force = (Mathf.Abs(accelerationInput) > 0.05f && !isBraking) ? torque : 0f;
                wheel_jt.motor = motar;
            }
        }
        else
        {
            JointMotor motar = wheelJoints[3].motor;
            motar.targetVelocity = angularSpeed * accelerationInput;
            motar.force = (Mathf.Abs(accelerationInput) > 0.05f && !isBraking) ? torque : 0f;
            wheelJoints[2].motor = motar;
            wheelJoints[3].motor = motar;
        }
    }

    void brakeLogic()
    {
        if (AllWheelBraking)
        {
            foreach (HingeJoint wheel_jt in wheelJoints)
            {
                ApplyMotor(0, wheel_jt);
            }
            return;
        }

        if (allWheelBrake)
        {
            if (isBraking)
            {
                foreach (HingeJoint wheel_jt in wheelJoints)
                {
                    ApplyMotor(0, wheel_jt);
                }
            }
        }
        else
        {
            if (isBraking)
            {
                ApplyMotor(0, wheelJoints[2]);
                ApplyMotor(0, wheelJoints[3]);
            }
        }
    }

    void ApplyMotor(float targetVelocity, HingeJoint hingeJoint)
    {
        hingeJoint.useMotor = true;
        JointMotor motor = hingeJoint.motor;
        motor.targetVelocity = targetVelocity;
        motor.force = torque;
        hingeJoint.motor = motor;
    }

    void turnLogic()
    {
        // Cache front joint spring
        JointSpring steerSpringFront = steeringJoint[1].spring;
        steerSpringFront.targetPosition = steeringInput * MaxTurnAngle;

        steeringJoint[0].spring = steerSpringFront;
        steeringJoint[1].spring = steerSpringFront;

        if (allWheelTurn)
        {
            // Cache rear joint spring
            JointSpring steerSpringRear = steeringJoint[2].spring;
            steerSpringRear.targetPosition = steeringInput * -MaxTurnAngle / 2;

            steeringJoint[2].spring = steerSpringRear;
            steeringJoint[3].spring = steerSpringRear;
        }
    }

    void AddDownForce()
    {
        rb.AddForce(Vector3.down * downForce);
    }

    void soundManager()
    {
        float maxspeed = 50f;
        float speed = LocalVelocity.z;
        float currentAngularSpeed = rb.angularVelocity.magnitude;

        // Adjust pitch with combined effect of speed and angular speed
        engineSound.pitch = Mathf.Lerp(
            minPitch,
            maxPitch,
            Mathf.Abs(speed + (accelerationInput + currentAngularSpeed / 10f) * 20f) / maxspeed
        );

        // Smoothly adjust engine volume
        if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.S))
        {
            engineSound.volume = Mathf.MoveTowards(engineSound.volume, 1f, 0.01f);
        }
        else
        {
            engineSound.volume = Mathf.MoveTowards(engineSound.volume, 0.5f, 0.01f);
        }
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.green;
        Gizmos.DrawLine(transform.position, transform.position + Vector3.down * Raydistance);

        if (!Application.isPlaying)
        {
            Gizmos.color = new Color(0, 1, 0, 0.2f);
            BoxCollider collider = transform.Find("Body Collider").GetComponent<BoxCollider>();
            Gizmos.DrawCube(collider.bounds.center, collider.bounds.size);

            Gizmos.color = Color.green;
            Gizmos.DrawWireCube(collider.bounds.center, collider.bounds.size);

            Vector3 groundCheckPlane = new Vector3(
                collider.size.x * 1.5f,
                0.05f,
                collider.size.z
            );

            Gizmos.color = new Color(0, 0, 1, 0.5f);
            Gizmos.DrawCube(transform.position + Vector3.down * Raydistance, 1.5f * groundCheckPlane);
            Gizmos.DrawWireCube(transform.position + Vector3.down * Raydistance, 1.5f * groundCheckPlane);
        }
    }
}
